#ifndef MY_ROOM_HPP
#define MY_ROOM_HPP

#include "util.hpp"
#include "db.hpp"
#include "online.hpp"

typedef enum {GAME_START, GAME_OVER} room_status;

#define BOARD_ROW 15
#define BOARD_COL 15

#define CHESS_WHITE 1
#define CHESS_BLACK 2


//游戏房间管理模块

//游戏房间的设计
class room
{
private:
    //房间ID
    uint64_t _room_id;
    //房间状态
    room_status _stat;
    //房间里面玩家的数量
    int _player_count;
    //白棋玩家ID
    uint64_t _white_id;
    //黑棋玩家ID
    uint64_t _black_id;
    //用户信息管理
    user_table* _tb_user;
    //在线用户管理
    online_manager* _online_user;
    //棋盘
    std::vector<std::vector<int>> _board;

private:

    //具体的棋盘判断胜利
    //row,col分别是行，列的位置，row_off,col_off是偏移位置
    bool five(int row, int col, int row_off, int col_off, int color)
    {
        //统计连续的数量
        int count = 1;
        int search_row = row + row_off;
        int search_col = col + col_off;

        while(search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL
            && _board[search_row][search_col] == color)
        {
            count++;
            //继续向外面扩展
            search_row += row_off;
            search_col += col_off;
        }

        search_row = row - row_off;
        search_col = col - col_off;

        while(search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL
            && _board[search_row][search_col] == color)
        {
            count++;
            //继续向外面扩展
            search_row -= row_off;
            search_col -= col_off;
        }

        return (count >= 5);
    }

    //检查当前走棋是否取得胜利
    uint64_t check_win(int row, int col, int color)
    {
        //如果当前走棋胜利，返回胜利的ID
        //否则返回0
        if(five(row, col, 0, 1, color) ||
           five(row, col, 1, 0, color) || 
           five(row, col, -1, 1, color) ||
           five(row, col, -1, -1, color))
        {
            return color == CHESS_WHITE ? _white_id : _black_id;
        }
        return 0;
    }

public:
    room(uint64_t room_id, user_table* tb_user, online_manager* online_user)
        : _room_id(room_id), _stat(GAME_START), _player_count(0),
          _white_id(0),_black_id(0),
          _tb_user(tb_user), _online_user(online_user),
          _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DLOG("%lu 房间创建成功", _room_id);
    }

    uint64_t id()
    {
        return _room_id;
    }

    room_status status()
    {
        return _stat;
    }

    int player_count()
    {
        return _player_count;
    }

    void add_white_user(uint64_t uid)
    {
        _white_id = uid;
        _player_count++;
    }

    void add_black_user(uint64_t uid)
    {
        _black_id = uid;
        _player_count++;
    }

    uint64_t get_white_user()
    {
        return _white_id;
    }

    uint64_t get_black_user()
    {
        return _black_id;
    }

    ~room()
    {
        DLOG("%lu 房间销毁成功", _room_id);
    }

    //处理下棋动作
    Json::Value handle_chess(Json::Value& req)
    {
        Json::Value json_resp = req;
        //1.当前请求的房间号是否与当前房间的房间号匹配
        uint64_t cur_uid = req["uid"].asUInt64();

        //2.判断房间中两个玩家是否都在线，任意一个不在线，就判断另一个玩家胜利
        //白棋玩家不在线
        if(_online_user->is_in_game_room(_white_id) == false)
        {
            json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线，不战而胜!";
            json_resp["winner"] = _black_id;

            return json_resp;
        }

        //黑棋玩家不在线
        if(_online_user->is_in_game_room(_black_id) == false)
        {
            json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线，不战而胜!";
            json_resp["winner"] = _white_id;

            return json_resp;
        }

        //3.获取走棋位置，判断当前走棋是否合理（位置是否以及被占用）
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();

        if(_board[chess_row][chess_col] != 0)
        {
            json_resp["optype"] = "put_chess";
            json_resp["result"] = false;
            json_resp["reason"] = "当前位置已经存在其他棋子！";
            return json_resp;
        }
        
        //4.判断是否有玩家胜利（从当前走棋位置开始判断是否存在五星连珠）
        int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;
        _board[chess_row][chess_col] = cur_color;

        uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
        if(winner_id != 0)
        {
            json_resp["reason"] = "五星连珠！";
        }

        json_resp["optype"] = "put_chess";
        json_resp["result"] = true;
        json_resp["row"] = chess_row;
        json_resp["col"] = chess_col;
        json_resp["winner"] = winner_id;
        return json_resp;
    }

    //处理聊天动作
    Json::Value handle_chat(Json::Value& req)
    {
        Json::Value json_resp = req;

        //判断是否在同一个房间

        //判断是否包括屏蔽消息
        std::string msg = req["message"].asString();
        auto pos = msg.find("垃圾");
        if(pos != std::string::npos)
        {
            json_resp["result"] = false; 
            json_resp["reason"] = "当前消息包含敏感信息，不能发送!";

             // 只发送给发送者本人，不广播给另一个玩家
            wssrv_t::connection_ptr conn = _online_user->get_conn_from_room(json_resp["uid"].asUInt64());
            if(conn.get() != nullptr)
            {
                std::string body;
                json_util::serialize(json_resp, body);
                conn->send(body);
            }
            return json_resp;
        }
        //广播消息
        json_resp["result"] = true;
        return json_resp;
    }

    //处理玩家退出房间动作
    void handle_exit(uint64_t uid)
    {
        //游戏在运行时，退出会对结果造成影响
        //游戏结束时退出，没有影响
        Json::Value json_resp;
        if(_stat == GAME_START)
        {
            uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
            json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线，不战而胜!";
            json_resp["room_id"] = (Json::UInt64)_room_id;
            json_resp["uid"] = (Json::UInt64)uid;
            json_resp["row"] = -1;
            json_resp["col"] = -1;
            json_resp["winner"] = winner_id;

            //更新用户信息表
            uint64_t lose_id = winner_id == _white_id ? _black_id : _white_id;
            _tb_user->win(winner_id);
            _tb_user->lose(lose_id);

            //防止在退出处理时更新一次结果，然后广播这个消息时再次更新结果
            //设置游戏状态为结束，防止重复处理
            _stat = GAME_OVER;

            boardcast(json_resp);
        }
        //总的玩家数量减少
        _player_count--;
        return;
    }

    //总的请求处理函数，在函数内部，区分请求类型
    //根据不同的请求调用不同的处理函数，得到响应进行广播
    void handle_request(Json::Value& req)
    {
        Json::Value json_resp;
        //1.检验房间号是否匹配
        uint64_t room_id = req["room_id"].asUInt64();
        if(room_id != _room_id)
        {
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false; 
            json_resp["reason"] = "房间号不匹配!";
            
            return boardcast(json_resp);
        }

        //2.根据不同的请求类型，调用不同的处理函数
        if(req["optype"].asString() == "put_chess")
        {
            json_resp = handle_chess(req);
            //如果有玩家胜利更新用户信息表
            if(json_resp["winner"].asUInt64() != 0)
            {
                //更新用户信息表
                uint64_t winner_id = json_resp["winner"].asUInt64();
                uint64_t lose_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(lose_id);
                //游戏退出更新数据后应该设置游戏结束的状态 
                _stat = GAME_OVER;
            }
        }
        else if(req["optype"].asString() == "chat")
        {
            json_resp = handle_chat(req);
            //如果有敏感信息直接返回，不广播
            if(json_resp["result"] == false)
            {
                return;
            }
        }
        else
        {
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false; 
            json_resp["reason"] = "未知请求类型";
        }

        return boardcast(json_resp);
    }

    //将指定的信息广播给房间里面的所有玩家
    void boardcast(Json::Value& resp)
    {
        //1.对要响应的信息进行序列化，将Json::Value中的数据序列化成为json格式字符串
        std::string body;
        json_util::serialize(resp, body);

        //2.获取房间中所有用户的通信连接
        //3.发送响应信息
        wssrv_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        if(wconn.get() != nullptr)
        {
            wconn->send(body);
        }

        wssrv_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        if(bconn.get() != nullptr)
        {
            bconn->send(body);
        }

        return;
    }
};

using room_ptr = std::shared_ptr<room>;

//游戏房间管理的设计
class room_manager
{
private:

    //游戏房间的计数
    uint64_t _next_rid;
    //用户信息管理句柄
    user_table* _tb_user;
    //在线用户管理句柄
    online_manager* _online_user;
    //互斥锁
    std::mutex _mutex;
    //建立房间ID与房间信息的映射关系
    std::unordered_map<uint64_t, room_ptr> _rooms;
    //建立用户ID与房间ID的映射关系
    std::unordered_map<uint64_t, uint64_t> _users;

public:

    //初始化房间ID计数器
    room_manager(user_table* ut, online_manager* om)
        : _next_rid(1), _tb_user(ut), _online_user(om)
    {
        DLOG("初始化游戏房间管理模块");
    }
    ~room_manager()
    {
        DLOG("游戏房间管理模块销毁");
    }

    //为两个用户创建房间，并且返回房间的智能指针管理对象
    room_ptr create_room(uint64_t uid1, uint64_t uid2)
    {
        //两个用户在游戏大厅匹配对战，分配游戏房间
        //1.首先检验两个用户是否都在游戏大厅里面，有一个不在就返回空指针
        if(_online_user->is_in_game_hall(uid1) == false)
        {
            DLOG("玩家id为%lu 现在不在大厅，创建房间失败！", uid1);
            return room_ptr();
        }

        if(_online_user->is_in_game_hall(uid2) == false)
        {
            DLOG("玩家id为%lu 现在不在大厅，创建房间失败！", uid2);
            return room_ptr();
        }

        //2.分配游戏房间，将用户信息添加到房间里面
        //先加锁保证线程安全
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rp(new room(_next_rid, _tb_user, _online_user));
        rp->add_white_user(uid1);
        rp->add_black_user(uid2);

        //3.将房间信息管理起来
        _rooms.insert(std::make_pair(_next_rid, rp));
        _users.insert(std::make_pair(uid1, _next_rid));
        _users.insert(std::make_pair(uid2, _next_rid));
        _next_rid++;
        //4.返回房间信息
        return rp;
        
    }

    //通过房间ID获取房间信息
    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if(it == _rooms.end())
        {
            return room_ptr();
        }
        return it->second;
    }

    //通过用户ID获取房间信息
    room_ptr get_room_by_uid(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto uit = _users.find(uid);
        if(uit == _users.end())
        {
            return room_ptr();
        }
        auto rid = uit->second;
        auto rit = _rooms.find(rid);
        if(rit == _rooms.end())
        {
            return room_ptr();
        }
        return rit->second;
    }

    //通过房间ID销毁房间
    void remove_room(uint64_t rid)
    {
        //房间信息是通过shared_ptr在_rooms中进行管理，因此只要将shared_ptr从_romms中移除
        //则shared_ptr计数器==0，外界没有对房间信息进行操作保存的情况下就会释放

        //1.通过房间ID找到房间信息
        room_ptr rp = get_room_by_rid(rid);
        if(rp.get() == nullptr)
        {
            return;
        }
        //2.通过房间信息，获取房间里面的所有用户ID
        uint64_t uid1 = rp->get_white_user();
        uint64_t uid2 = rp->get_black_user();
        //3.移除房间信息管理的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);
        //4.移除房间管理信息
        _rooms.erase(rid);
        
    }

    //删除房间中的指定用户，如果房间中没有用户了，就销毁房间，用户连接断开时被调用
    void remove_room_user(uint64_t uid)
    {
        room_ptr rp = get_room_by_uid(uid);
        if(rp.get() == nullptr)
        {
            return;
        }
        //处理用户退出动作
        rp->handle_exit(uid);
        //房间没有用户了，就销毁房间
        if(rp->player_count() == 0)
        {
            remove_room(rp->id());
        }

    }

};

#endif